#include "../include/check_crash.h"
#include "../include/object.h"
#include "../include/event.h"
#include "../include/global_settings.h"
#include "../include/crash_handler.h"
#include <cmath>
#include <vector>
#include <cstring>
using std::vector;

#define Max(x, y) ((x) > (y) ? (x) : (y))
#define Min(x, y) ((x) > (y) ? (y) : (x))

const float INF = 100.0f;

struct RECT
{
	float left, right;
	float top, bottom;
};

enum SIDE
{
	UP = 1,
	LEFT,
	DOWN,
	RIGHT,
	SIDE_COUNT
};

//local functions
int checkCrash(hObject obj1, hObject obj2);
RECT getRect(hObject obj);
float segOverlapped(float a1, float b1, float a2, float b2);
bool rectOverlapped(RECT rect1, RECT rect2);
void evtExecuteEvent(hObject obj1, hObject obj2, int side);
void decideMove(hObject obj, float &dx, float &dy);
bool relativelySolid(hObject obj1, hObject obj2);

//wrapper for Object functinos
hObject evtFirstObject(iObjectIterator &i);
hObject evtNextObject(iObjectIterator &i);

//local functions
int checkCrash(hObject obj1, hObject obj2)
{
	int ret = 0;
	RECT rect1 = getRect(obj1), rect2 = getRect(obj2);
	float overlap_x = segOverlapped(rect1.left, rect1.right, rect2.left, rect2.right);
	float overlap_y = segOverlapped(rect1.bottom, rect1.top, rect2.bottom, rect2.top);
	float cent_x = (rect1.left + rect1.right) - (rect2.left + rect2.right);
	float cent_y = (rect1.top + rect1.bottom) - (rect2.top + rect2.bottom);

	if(fabs(overlap_x) <= PRECISION && overlap_y >= PRECISION)
	{
		if(rect1.left < rect2.left)ret = LEFT;
		else
			ret = RIGHT;
	}
	else if(fabs(overlap_y) <= PRECISION && overlap_x >= PRECISION)
	{
		if(rect1.top < rect2.top)ret = DOWN;
		else
			ret = UP;
	}
	else
	{
		if(overlap_x >= PRECISION && overlap_y >= PRECISION)
		{
			if(overlap_x * fabs(cent_y) - overlap_y * fabs(cent_x) >= 0)
			{
				if(cent_y >= 0)ret = UP;
				else
					ret = DOWN;
			}
			else
			{
				if(cent_x >= 0)ret = RIGHT;
				else
					ret = LEFT;
			}
		}
	}
	return ret;
}

//wrapper for Object functinos

hObject evtFirstObject(iObjectIterator &i)
{
	i = (iObjectIterator)0xfffffff1;
	return getPlayer(0);
}

hObject evtNextObject(iObjectIterator &i)
{
	hObject ret;
	if((((unsigned int)i) & ~0x0f) == 0xfffffff0)  //note : support no more than SIXTEEN players
	{
		int id = (unsigned int)i & 0x0f;
		if(id == getPlayerCount())
		{
			ret = firstObject(&i);
		}
		else
		{
			ret = getPlayer(id);
			i = (iObjectIterator)(0xfffffff0 | (id + 1));
		}
	}
	else
	{
		ret = nextObject(&i);
	}
	return ret;
}

template <int size>
class FloatArrayWrapper
{
public :
	float arr[size];
	FloatArrayWrapper()
	{
		for(int i = 0; i < size; i++)arr[i] = 0;
	}
	FloatArrayWrapper<size> &operator=(FloatArrayWrapper<size> &w)
	{
		for(int i = 0; i < size; i++)this->arr[i] = w.arr[i];
		return *this;
	}
};
typedef FloatArrayWrapper<SIDE_COUNT> FloatArray;

void decideMove(hObject obj, float &dx, float &dy)
{
	iObjectIterator i;
	RECT rect_obj = getRect(obj);
	hObject obj1 = evtFirstObject(i);
	vector<FloatArray> rec;
	bool passed_obj = false;
	while(obj1 != 0)
	{
		pObject pobj1 = getData(obj1);
		if(obj != obj1)
		{
			if(relativelySolid(obj, obj1))
			{
				FloatArray currec;
				RECT rect_obj1 = getRect(obj1);
				if(rectOverlapped(rect_obj, rect_obj1))
				{
					float adjust_rate;
					if(!pobj1->fixed && passed_obj)adjust_rate = 2.0f;
					else
						adjust_rate = 1.0f;

					currec.arr[0] = INF;
					currec.arr[UP] = fabs(rect_obj.bottom - rect_obj1.top) / adjust_rate;
					currec.arr[DOWN] = fabs(rect_obj.top - rect_obj1.bottom) / adjust_rate;
					currec.arr[LEFT] = fabs(rect_obj.right - rect_obj1.left) / adjust_rate;
					currec.arr[RIGHT] = fabs(rect_obj.left - rect_obj1.right) / adjust_rate;
				}
				else
				{
					memset(currec.arr, 0, sizeof(currec.arr));
				}
				rec.push_back(currec);
			}
		}
		else
			passed_obj = true;

		obj1 = evtNextObject(i);
	}

	int choice1[] = {0, UP, DOWN}, choice2[] = {0, LEFT, RIGHT};
	float mov[2];
	float min_dist = INF;
	for(int i = 0; i < sizeof(choice1) / sizeof(int); i++)
	{
		for(int j = 0; j < sizeof(choice2) / sizeof(int); j++)
		{
			if(choice1[i] == 0 && choice2[j] == 0)continue;
			mov[0] = mov[1] = 0.0f;
			for(unsigned int k = 0; k < rec.size(); k++)
			{
				if(rec[k].arr[choice1[i]] < rec[k].arr[choice2[j]])
				{
					mov[0] = Max(mov[0], rec[k].arr[choice1[i]]);
				}
				else
				{
					mov[1] = Max(mov[1], rec[k].arr[choice2[j]]);
				}
			}
			if(mov[0] + mov[1] < min_dist && mov[0] <= ACCEPTABLE_DIST * 2 && mov[1] <= ACCEPTABLE_DIST * 2) //adjust too much is not allowed
			{
				min_dist = mov[0] + mov[1];
				dx = (choice2[j] == RIGHT ? mov[1] : -mov[1]);
				dy = (choice1[i] == UP ? mov[0] : -mov[0]);
			}
		}
	}
}

RECT getRect(hObject obj)
{
	RECT ret;
	pObject pobj = getData(obj);
	ret.left = pobj->pos.x + pobj->size.x * (1 - EFFECTIVE_AREA_X) / 2;
	ret.right = pobj->pos.x + pobj->size.x * (1 + EFFECTIVE_AREA_X) / 2;
	ret.bottom = pobj->pos.y;
	ret.top = pobj->pos.y + pobj->size.y;
	return ret;
}

float segOverlapped(float a1, float b1, float a2, float b2)
{
	return Min(b1, b2) - Max(a1, a2);
}

bool rectOverlapped(RECT rect1, RECT rect2)
{
	if(segOverlapped(rect1.left, rect1.right, rect2.left, rect2.right) >= -PRECISION
		&& segOverlapped(rect1.bottom, rect1.top, rect2.bottom, rect2.top) >= -PRECISION)
		return true;
	return false;
}

void evtExecuteEvent(hObject obj1, hObject obj2, int side)
{
	switch(side)
	{
	case UP :
		stomp(obj1, obj2);
		break;
	case DOWN :
		stomp(obj2, obj1);
		break;
	case LEFT :
		crash(obj1, obj2);
		break;
	case RIGHT :
		crash(obj2, obj1);
		break;
	default : break;
	}
}

bool relativelySolid(hObject obj1, hObject obj2)
{
	pObject pobj1 = getData(obj1), pobj2 = getData(obj2);
	if(pobj1->solid == SOLID && pobj2->solid == SOLID)return true;
	if(pobj1->solid == HALF_SOLID && pobj2->solid == SOLID)
	{
		pObject temp = pobj1;
		pobj1 = pobj2;
		pobj2 = temp;
	}
	if(pobj1->solid == SOLID && pobj2->solid == HALF_SOLID)
	{
		if(pobj2->pos.y + pobj2->size.y - pobj1->pos.y <= ACCEPTABLE_DIST
			&& pobj2->pos.y +pobj2->size.y - pobj1->pos.y >= -PRECISION
			&& pobj1->speed.y <= -PRECISION)return true;
	}
	return false;
}

//external functions
void adjustPos()
{
	iObjectIterator i;
	hObject obj = evtFirstObject(i);
	while(obj)
	{
		pObject pobj = getData(obj);
		if(!pobj->fixed)
		{
			float dx, dy;
			decideMove(obj, dx, dy);
			pobj->pos.x += dx;
			pobj->pos.y += dy;
		}
		obj = evtNextObject(i);
	}
}

void checkCrash()
{
	iObjectIterator i;
	hObject obj1 = evtFirstObject(i);
	while(obj1)
	{
		iObjectIterator j = i;
		hObject obj2 = evtNextObject(j);
		while(obj2)
		{
			int side = checkCrash(obj1, obj2);
			evtExecuteEvent(obj1, obj2, side);
			obj2 = evtNextObject(j);
		}
		obj1 = evtNextObject(i);
	}
}

int Die(hObject obj){

	pObject pobj = getData(obj);
	pobj->alive = 0;
	pobj->fixed = 1;
	pobj->solid = 0;
	pobj->weapon = 0;
	pobj->size.y = 0.01f;
	if(pobj->hp > 0)pobj->hp--;
	if(pobj->hp)
	{
		EventInfo dropout = {obj, 0, evt_respawn_id, 10};
		addEvent(&dropout, EVT_NORMAL_PRIORITY);
	}
   return 0;
}